/**************************************************************************//**
 * @file     drv_gpio.c
 * @version  V1.00
 * $Revision: 1 $
 * $Date: 16/02/17 2:44p $
 * @brief    NUC970 GPIO driver source file
 *
 * @note
 * SPDX-License-Identifier: Apache-2.0
 * @Copyright (C) 2021 THEWON. All rights reserved.
*****************************************************************************/
#include <board.h>

#if (defined(BSP_USING_GPIO) && defined(RT_USING_PIN))

#include <rtdevice.h>
#include <rthw.h>
#include <stdlib.h>
#include "drv_gpio.h"
#include "nuc970_gpio.h"
#include "nuc970_mfp.h"
#include "drv_sys.h"
#include "nu_bitutil.h"

/* Private define ---------------------------------------------------------------*/

#define IRQ_MAX_NUM         (NU_PORT_MAX)            //Max support 32

typedef void (*IRQ_CALLBACK)(void *param);

typedef struct 
{
    IRQ_CALLBACK    IRQCallback[NU_PORT_MAX];
    void*           IRQUserData[NU_PORT_MAX];
} GPIO_IRQ_CFG;

/* Private functions ------------------------------------------------------------*/
static rt_err_t nu_gpio_attach_irq(rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args);
static rt_err_t nu_gpio_detach_irq(rt_int32_t pin);
static rt_err_t nu_gpio_irq_enable(rt_base_t pin, rt_uint32_t enabled);
static rt_base_t nu_gpio_pin_get(const char *name);

/* Private variables ------------------------------------------------------------*/
static struct rt_pin_irq_hdr pin_irq_hdr_tab[IRQ_MAX_NUM];

static rt_uint32_t irqMask = 0x0;
static GPIO_IRQ_CFG irqConfig;

/* Functions define ------------------------------------------------------------*/

static rt_err_t nu_port_check(rt_int32_t pin)
{
    if (NU_GET_PORT(pin) >= NU_PORT_MAX)
        return -(RT_ERROR);

    return RT_EOK;
}

static rt_err_t nu_find_irqindex(rt_uint32_t pin_index)
{
    rt_int32_t irqIndex;
    rt_int32_t irqStatus = irqMask;

    // Find index of pin is attached in pool.
    while ((irqIndex = nu_ctz(irqStatus)) < IRQ_MAX_NUM) // Count Trailing Zeros ==> Find First One
    {
        if (pin_irq_hdr_tab[irqIndex].pin == pin_index)
            return irqIndex;

        irqStatus &= ~(1 << irqIndex);
    }

    return -(RT_ERROR);
}

static void pin_irq_hdr(rt_uint32_t isr, rt_uint32_t port)
{
    rt_int32_t irqIndex, i;
    rt_int32_t pin = port << 4;

    while ((i = nu_ctz(isr)) < 16)// Count Trailing Zeros ==> Find First One
    {
        int pin_mask = (1 << i);
        irqIndex = nu_find_irqindex(pin + i);
        if (irqIndex != -(RT_ERROR))
        {
            if (pin_irq_hdr_tab[irqIndex].hdr)
            {
                pin_irq_hdr_tab[irqIndex].hdr(pin_irq_hdr_tab[irqIndex].args);
            }
        }
        // Clear the served bit.
        isr &= ~pin_mask;
    }
}

static rt_base_t nu_gpio_pin_get(const char *name)
{
    long number;

    /* Get pin number by name,such as PA.0, PF12 */
    if ((name[2] == '\0') || ((name[2] == '.') && (name[3] == '\0')))
        return -(RT_EINVAL);

    if ((name[2] == '.'))
        number = atol(&name[3]);
    else
        number = atol(&name[2]);

    if (number > 15 || number < 0)
        return -(RT_EINVAL);

    if (name[1] >= 'A' && name[1] <= 'H')
        return ((name[1] - 'A') << 4) + number;

    if (name[1] >= 'a' && name[1] <= 'h')
        return ((name[1] - 'a') << 4) + number;

    return -(RT_EINVAL);
}

void nu_gpio_mode(rt_base_t pin, rt_base_t mode)
{
    GPIO_TypeDef *GPIOx;
    rt_uint32_t reg, mask, bit;
    register rt_base_t level;

    if (nu_port_check(pin))
        return;

    GPIOx = (GPIO_TypeDef*)(GPIOA_BA + (NU_GET_PORT(pin) << 6));
    bit = NU_GET_BIT(pin);

    reg = REG_SYS_GPA_MFPL + (NU_GET_MFP(pin) << 2);
    if (bit < 8) {
        mask = 0xF << (bit*4);
    } else {
        mask = 0xF << ((bit-8)*4);
    }

    /* disable interrupt */
    level = rt_hw_interrupt_disable();

    outpw(reg, inpw(reg) & (~mask));

    mask = NU_GET_BIT_MASK(bit);
    switch(mode)
    {
    case PIN_MODE_INPUT_PULLUP :
        reg = GPIOx->PDEN;
        GPIOx->PDEN = reg & (~mask);
        reg = GPIOx->DIR;
        GPIOx->DIR = reg & (~mask);
        reg = GPIOx->PUEN;
        GPIOx->PUEN = reg | mask;
    break;
    case PIN_MODE_INPUT_PULLDOWN :
        reg = GPIOx->PUEN;
        GPIOx->PUEN = reg & (~mask);
        reg = GPIOx->DIR;
        GPIOx->DIR = reg & (~mask);
        reg = GPIOx->PDEN;
        GPIOx->PDEN = reg | mask;
    break;
    case PIN_MODE_OUTPUT :
    case PIN_MODE_OUTPUT_OD :
        reg = GPIOx->PDEN;
        GPIOx->PDEN = reg & (~mask);
        reg = GPIOx->PUEN;
        GPIOx->PUEN = reg & (~mask);
        reg = GPIOx->DIR;
        GPIOx->DIR = reg | mask;
    break;
    case PIN_MODE_INPUT :
        reg = GPIOx->PDEN;
        GPIOx->PDEN = reg & (~mask);
        reg = GPIOx->PUEN;
        GPIOx->PUEN = reg & (~mask);
        reg = GPIOx->DIR;
        GPIOx->DIR = reg & (~mask);
//        reg = GPIOx->ICEN;
//        GPIOx->ICEN = reg & (~mask);
//        reg = GPIOx->DBEN;
//        GPIOx->DBEN = reg & (~mask);
    break;
    case PIN_MODE_OUTPUT_PULLUP :
        reg = GPIOx->PDEN;
        GPIOx->PDEN = reg & (~mask);
        reg = GPIOx->DIR;
        GPIOx->DIR = reg | mask;
        reg = GPIOx->PUEN;
        GPIOx->PUEN = reg | mask;
    break;
    case PIN_MODE_OUTPUT_PULLDOWN :
        reg = GPIOx->PUEN;
        GPIOx->PUEN = reg & (~mask);
        reg = GPIOx->DIR;
        GPIOx->DIR = reg | mask;
        reg = GPIOx->PDEN;
        GPIOx->PDEN = reg | mask;
    break;
    default:
    break;
    }
    /* enable interrupt */
    rt_hw_interrupt_enable(level);
}

void nu_gpio_write(rt_base_t pin, rt_base_t value)
{
    GPIO_TypeDef *GPIOx;
    rt_uint32_t reg, mask;
    register rt_base_t level;

    if (nu_port_check(pin))
        return;

    GPIOx = (GPIO_TypeDef*)(GPIOA_BA + (NU_GET_PORT(pin) << 6));
    mask = NU_GET_BIT_MASK(NU_GET_BIT(pin));

    /* disable interrupt */
    level = rt_hw_interrupt_disable();
    reg = GPIOx->DATAOUT;
    if (value == 0) {
        GPIOx->DATAOUT = reg & (~mask);
    } else {
        GPIOx->DATAOUT = reg | mask;
    }
    /* enable interrupt */
    rt_hw_interrupt_enable(level);
}

int nu_gpio_read(rt_base_t pin)
{
    GPIO_TypeDef *GPIOx;
    rt_uint32_t reg, mask;

    if (nu_port_check(pin))
        return GPIO_DRV_LOW;

    GPIOx = (GPIO_TypeDef*)(GPIOA_BA + (NU_GET_PORT(pin) << 6));
    mask = NU_GET_BIT_MASK(NU_GET_BIT(pin));

    reg = GPIOx->DATAIN;
    return ((reg & mask) ? 1 : 0);
}

static rt_err_t nu_gpio_attach_irq(rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args)
{
    register rt_base_t level;
    rt_int32_t irqIndex;

    if (nu_port_check(pin))
        return -(RT_ERROR);

    level = rt_hw_interrupt_disable();

    // Find index of pin is attached in pool.
    if ((irqIndex = nu_find_irqindex(pin)) >= 0)
        goto exit_attach_irq;

    // Find available index of pin in pool.
    if ((irqIndex = nu_cto(irqMask)) < IRQ_MAX_NUM)  // Count Trailing Ones ==> Find First Zero
        goto exit_attach_irq;

    rt_hw_interrupt_enable(level);

    return -(RT_EBUSY);

exit_attach_irq:

    pin_irq_hdr_tab[irqIndex].pin = pin;
    pin_irq_hdr_tab[irqIndex].hdr = hdr;
    pin_irq_hdr_tab[irqIndex].mode = mode;
    pin_irq_hdr_tab[irqIndex].args = args;

    irqMask |= (1 << irqIndex);
    rt_hw_interrupt_enable(level);

    return RT_EOK;
}

static rt_err_t nu_gpio_detach_irq(rt_int32_t pin)
{
    register rt_base_t level;
    rt_int32_t irqIndex;
    rt_int32_t irqStatus;

    if (nu_port_check(pin))
        return -(RT_ERROR);

    level = rt_hw_interrupt_disable();

    irqStatus = irqMask;

    // Find index of pin is attached in pool.
    while ((irqIndex = nu_ctz(irqStatus)) < IRQ_MAX_NUM)// Count Trailing Zeros ==> Find First One
    {
        if (pin_irq_hdr_tab[irqIndex].pin == pin)
        {
            pin_irq_hdr_tab[irqIndex].pin = PIN_IRQ_PIN_NONE;
            pin_irq_hdr_tab[irqIndex].hdr = RT_NULL;
            pin_irq_hdr_tab[irqIndex].mode = PIN_IRQ_MODE_RISING;
            pin_irq_hdr_tab[irqIndex].args = RT_NULL;
            irqMask &= ~(1 << irqIndex);
            break;
        }
        irqStatus &= ~(1 << irqIndex);
    }

    rt_hw_interrupt_enable(level);
    return RT_EOK;
}

static void nu_gpio_isr(int vector, void *param)
{
    rt_uint32_t reg;
    int i, mask;

    reg = inpw(REG_AIC_ISRH);
    mask = 0x02000000;
    reg = inpw(REG_GPIO_ISR);
    // for (i = 0; i < NU_PORT_MAX; i++) {
        // if ((reg & (1 << i))) {
            // pin_irq_hdr(inpw(REG_GPIOA_ISR + (i<<6)), i);
        // }
    // }
    reg = inpw(REG_GPIO_ISR);
    for (i = 0; i < NU_PORT_MAX; i++) {
        if (reg & (1 << i)) {
            if (irqConfig.IRQCallback[i] != NULL) {
                irqConfig.IRQCallback[i](irqConfig.IRQUserData[i]);
            }
        }
    }

    outpw(REG_AIC_SCCRH, 0x02000000);   /* Clear interrupt */
}

static rt_err_t nu_gpio_irq_enable(rt_base_t pin, rt_uint32_t enabled)
{
    GPIO_TypeDef *GPIOx;
    rt_int32_t portIndex, irqIndex;
    rt_err_t ret = RT_EOK;
    rt_uint32_t reg, mask;
    register rt_base_t level;

    if (nu_port_check(pin))
        return -(RT_ERROR);

    level = rt_hw_interrupt_disable();

    irqIndex = nu_find_irqindex(pin);
    if (irqIndex == -(RT_ERROR)) {
        ret = -RT_ERROR;
        goto exit_irq_enable;
    }

    portIndex = NU_GET_PORT(pin);
    GPIOx = (GPIO_TypeDef*)(GPIOA_BA + (portIndex << 6));
    mask = NU_GET_BIT_MASK(NU_GET_BIT(pin));

    if (enabled == PIN_IRQ_ENABLE) {
        switch(pin_irq_hdr_tab[irqIndex].mode) {
        case PIN_IRQ_MODE_FALLING :
            reg = GPIOx->IMD;
            GPIOx->IMD = reg & (~mask);
            reg = GPIOx->IREN;
            GPIOx->IREN = reg & (~mask);
            reg = GPIOx->IFEN;
            GPIOx->IFEN = reg | mask;
        break;
        case PIN_IRQ_MODE_RISING :
            reg = GPIOx->IMD;
            GPIOx->IMD = reg & (~mask);
            reg = GPIOx->IREN;
            GPIOx->IREN = reg | mask;
            reg = GPIOx->IFEN;
            GPIOx->IFEN = reg & (~mask);
        break;
        case PIN_IRQ_MODE_RISING_FALLING :
            reg = GPIOx->IMD;
            GPIOx->IMD = reg & (~mask);
            reg = GPIOx->IREN;
            GPIOx->IREN = reg | mask;
            reg = GPIOx->IFEN;
            GPIOx->IFEN = reg | mask;
        break;
        case PIN_IRQ_MODE_LOW_LEVEL :
            reg = GPIOx->IMD;
            GPIOx->IMD = reg | mask;
            reg = GPIOx->IREN;
            GPIOx->IREN = reg & (~mask);
            reg = GPIOx->IFEN;
            GPIOx->IFEN = reg | mask;
        break;
        case PIN_IRQ_MODE_HIGH_LEVEL :
            reg = GPIOx->IMD;
            GPIOx->IMD = reg | mask;
            reg = GPIOx->IREN;
            GPIOx->IREN = reg | mask;
            reg = GPIOx->IFEN;
            GPIOx->IFEN = reg & (~mask);
        break;
        default:
        break;
        }

        irqConfig.IRQCallback[portIndex] = pin_irq_hdr_tab[irqIndex].hdr;
        irqConfig.IRQUserData[portIndex] = pin_irq_hdr_tab[irqIndex].args;
        rt_hw_interrupt_umask(GPIO_IRQn);
    }
    else
    {
        reg = GPIOx->IMD;
        GPIOx->IMD = reg & (~mask);
        reg = GPIOx->IREN;
        GPIOx->IREN = reg & (~mask);
        reg = GPIOx->IFEN;
        GPIOx->IFEN = reg & (~mask);

        irqConfig.IRQCallback[portIndex] = RT_NULL;
        irqConfig.IRQUserData[portIndex] = RT_NULL;
    }

exit_irq_enable:
    /* enable interrupt */
    rt_hw_interrupt_enable(level);

    return ret;
}

static struct rt_pin_ops nu_gpio_ops =
{
    nu_gpio_mode,
    nu_gpio_write,
    nu_gpio_read,
    nu_gpio_attach_irq,
    nu_gpio_detach_irq,
    nu_gpio_irq_enable,
    nu_gpio_pin_get,
};

int rt_hw_pin_init(void)
{
    rt_int32_t irqIndex;

    MFP_GPIOInit();

    irqMask = 0;
    for (irqIndex = 0; irqIndex < IRQ_MAX_NUM ; irqIndex++)
    {
        pin_irq_hdr_tab[irqIndex].pin = PIN_IRQ_PIN_NONE;
        pin_irq_hdr_tab[irqIndex].hdr = RT_NULL;
        pin_irq_hdr_tab[irqIndex].mode = PIN_IRQ_MODE_RISING;
        pin_irq_hdr_tab[irqIndex].args = RT_NULL;
    }

    rt_hw_interrupt_install(GPIO_IRQn, nu_gpio_isr, (void *)(0), "gpio");
    rt_hw_interrupt_set_priority(GPIO_IRQn, IRQ_LEVEL_7);
    rt_hw_interrupt_set_type(GPIO_IRQn, HIGH_LEVEL_SENSITIVE);

    return rt_device_pin_register("gpio", &nu_gpio_ops, RT_NULL);
}

#endif // (defined(BSP_USING_GPIO) && defined(RT_USING_PIN))
